package ks.framework.communicator;

import java.util.*;

import ks.framework.common.Message;
import ks.framework.interfaces.ICommunicator;
import ks.framework.interfaces.IServerConnection;

/**
 * Responsible for all communication from the server to its clients.
 * <p>
 * Communicator will need access to an IUserManager object to be able to
 * identify the set of active uers during a broadcast.
 * 
 * Note that we read Commands from the client and write Messages
 * to the client.
 * 
 * @author George T. Heineman (heineman@cs.wpi.edu)
 */
public class Communicator implements IServerConnection {

	/** Record ICommunicator entities on behalf of the server with regard to a client. */
	private Hashtable<String,ICommunicator> communicationHash = new Hashtable<String,ICommunicator>();

	/**
	 * Distribute message based upon its status.
	 * <p>
	 * If the message is not a broadcast message, it is only sent to the
	 * designated recipient of the message.
	 * <p>
	 * If the message is a broadcast message and the designated recipient of
	 * the message is different from the originator, then the message is broadcast
	 * to all connected users EXCEPT the originator.
	 * <p>
	 * To produce a message to be broadcast to all connected users INCLUDING
	 * the originator of the message, just make sure that the recipient is
	 * equal to the originator. 
	 * <p>
	 * Please recognize this sequence of logic as a glorified hack.
	 * 
	 * @param m     Message to be delivered based upon its recipient status.
	 * @return      true if at least one message was sent; otherwise false
	 */
	public boolean distribute(Message m) {
		if (m.isBroadcast()) {
			// send to originator ONLY IF recipient is also set to the originator,
			String originator = m.getOriginator();
			String recipient = m.getRecipient();
			
			boolean atLeastOneSent = false;
			if (originator != null && recipient != null && 
				originator.equals(recipient)) {
				atLeastOneSent |= output(originator, m);
			}
			
			// and now distribute to everyone else.
			atLeastOneSent |= distribute(communicationHash.keySet().iterator(), m, true);
			return atLeastOneSent;
		}
		
		String recipient = m.getRecipient();
		return output(recipient, m);
	}

	/**
	 * Distribute command to given user list.
	 * <p>
	 * Regardless of whether the given message is a broadcast, it is delivered
	 * to all active users in the given iterator (which may include the originator
	 * of the message).
	 * 
	 * @param users     Iterator (of String) of users. 
	 * @param m         Message to be delivered to all active users
	 * @return          true if at least one message was sent; otherwise false
	 */
	public boolean distribute(Iterator<String> users, Message m) {
		return distribute(users, m, false);
	}

	// helper method.
	private boolean distribute(Iterator<String> users, Message m, boolean skipSelf) {
		
		// for each user, send message
		String from = m.getOriginator();
		boolean atLeastOneSent = false;
		while (users.hasNext()) {
			String toUser = users.next();

			// Skip SELF
			if (skipSelf && toUser.equals(from)) {
				continue;
			}
			
			atLeastOneSent |= output(toUser, m);
		}
		
		return atLeastOneSent;
	}
	
	/**
	 * Register with the communicator the means to interact with given userName.
	 * <p>
	 * Cannot connect the same user twice.
	 * 
	 * @param userName    String representing user connecting to server
	 * @param agent       ICommunicator object managing our communication.
	 * @exception         agent is already associated with an existing user.
	 */
	public boolean connectUser(String userName, ICommunicator agent) throws IllegalArgumentException {
		ICommunicator ic = communicationHash.get(userName);
		if (ic != null) { return false; }
		
		// make sure agent isn't already in the hash table
		for (String exist : communicationHash.keySet()) {
			ICommunicator existAgent = communicationHash.get(exist);
			if (existAgent == agent) {
				throw new IllegalArgumentException("That agent is already associated with " + exist);
			}
		}
		
		communicationHash.put (userName, agent);
		return true;
	}
	
	/**
	 * Remove user from communication set.
	 * 
	 * @param userName    String representing user connecting to server
	 */
	public boolean disconnectUser(String userName) {
		ICommunicator ic = communicationHash.get(userName);
		if (ic == null) { return false; }
		
		ic.close();
		communicationHash.remove (userName);
		return true;
	}

	/**
	 * Helper method to ensure delivery of message to agent for given player.
	 * 
	 * @param player      designated recipient
	 * @param m           message for delivery
	 */
	private boolean output(String player, Message m) {
		ICommunicator agent = (ICommunicator) communicationHash.get (player);
		if (agent == null) {
			System.err.println ("Attempt to send message to null player.");
			return false;  // not much to do!
		}

		// consider it delivered.
		return agent.writeObject (m);
	}
	
	/** 
	 * Determine if given user is online.
	 * 
	 * @param user
	 * @return
	 */
	public boolean isOnline(String user) {
		return communicationHash.containsKey(user);
	}
 
	/** 
	 * Determine set of online user names.
	 * 
	 * @return Collection
	 */
	public Collection<String> getOnlineUsers() {
		return communicationHash.keySet();
	}
	
	/** 
	 * Disconnect should user be logged in.
	 * 
	 * @param username
	 * @return
	 */
//	public boolean disconnect(String username) {
//		ICommunicator ic = communicationHash.get(username);
//		if (ic == null) { return false; }
//		
//		ic.close();
//		return true;
//	}
}